package com.googlecode.protoserv;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.google.protobuf.ByteString;

public class Base64 {
  private static final char[] ENCODE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
      .toCharArray();

  private static final int[] DECODE;
  static {
    DECODE = new int[256];
    for (int i = 0; i < DECODE.length; ++i) {
      DECODE[i] = -1;
    }
    for (int i = 0; i < ENCODE.length; ++i) {
      DECODE[ENCODE[i]] = i;
    }
  }

  public static String encode(ByteString data) {
    StringBuilder buffer = new StringBuilder((int) (data.size() * 1.34));
    try {
      encode(data, buffer);
    } catch (IOException e) {
    }
    return buffer.toString();
  }

  public static void encode(ByteString data, Appendable output)
      throws IOException {
    int pad = 0;
    for (int i = 0; i < data.size(); i += 3) {
      int b = ((data.byteAt(i) & 0xFF) << 16) & 0xFFFFFF;
      if (i + 1 < data.size()) {
        b |= (data.byteAt(i + 1) & 0xFF) << 8;
      } else {
        pad++;
      }
      if (i + 2 < data.size()) {
        b |= (data.byteAt(i + 2) & 0xFF);
      } else {
        pad++;
      }
      while ((b & 0xFFFFFF) != 0) {
        int c = (b & 0xFC0000) >> 18;
        output.append(ENCODE[c]);
        b <<= 6;
      }
    }
    for (int j = 0; j < pad; j++) {
      output.append("=");
    }
  }

  public static void decode(InputStream in, int size, OutputStream out)
      throws IOException {
    for (int i = 0; i < size; i += 4) {
      int ch = in.read();
      int b = 0;
      if (DECODE[ch] != -1) {
        b = (DECODE[ch] & 0xFF) << 18;
      } else {
        i++;
        continue;
      }
      if (i + 1 < size && DECODE[ch = in.read()] != -1) {
        b = b | ((DECODE[ch] & 0xFF) << 12);
      }
      if (i + 2 < size && DECODE[ch = in.read()] != -1) {
        b = b | ((DECODE[ch] & 0xFF) << 6);
      }
      if (i + 3 < size && DECODE[ch = in.read()] != -1) {
        b = b | (DECODE[ch] & 0xFF);
      }
      while ((b & 0xFFFFFF) != 0) {
        int c = (b & 0xFF0000) >> 16;
        out.write((byte) c);
        b <<= 8;
      }
    }
  }

  public static ByteString decode(String text) {
    ByteString.Output output = ByteString.newOutput(text.length());
    try {
      decode(new ByteArrayInputStream(text.getBytes()), text.length(), output);
    } catch (IOException e) {
    }
    return output.toByteString();
  }
}
